#include "kthread.h"
#include "param.h"
#include "types.h"
#include "user.h"
#include "fs.h"
#include "fcntl.h"
#include "syscall.h"
#include "traps.h"
#include "memlayout.h"

#define LONG_EATING_PROCESS_ITERATIONS 100000
#define SHORT_EATING_PROCESS_ITERATIONS 1000

//Our menu consists of three kinds of food: Salad, Pasta and Steak, assigned values 0, 1 and 2 respectively.
#define SALAD 0
#define PASTA 1
#define STEAK 2

#define Students_Initial 0
#define Students_Joining 1
#define Num_of_seats 2
#define Salad_Buffer_Size 3
#define Pasta_Buffer_Size 4
#define Steak_Buffer_Size 5

#define TABLE 0
#define STUDENTS 1

#define p_student 3
#define p_waiter_host 1
#define p_normal 2

int iter_long = LONG_EATING_PROCESS_ITERATIONS;
int iter_short = SHORT_EATING_PROCESS_ITERATIONS;
int totalThreadsNum = 1;

int exitFlag = 0;
int waitingQueue = 0;
int idMAX = 0;

//int totalRTime = 0;
int _rTime = 0;
int _wTime = 0;

struct buffer{
    int cv_full;
    int cv_empty;    
    int mutex_lock;
    int counter;
    int max;
};

struct buffer pastaBuffer;
struct buffer steakBuffer;
struct buffer saladBuffer;
struct buffer tableBuffer;

struct buffer food[3];


struct thread_t{
    int tid;
    void* stack;
    int stud_id;
};

struct thread_t* threads;

struct {
    int students_waiting;
    int students_done;
    int students_max;
} restaurant;


//When simulation ends, print the results of the simulation
//TODO: totalElapsedTime == uptime() - ptable.proc->ctime
void printStats(){
    //In order to calculate average statistics per thread,
    //we will take total run_time and total_WAITING_time of the process and divide it by number of created threads.
    printf(1,"Simulation: User-Level-Threads\n");
    printf(1,"Total run time is: %d\n",_rTime);
    printf(1,"Average run time per user-level-thread is: %d\n",_rTime/(tableBuffer.max+4));
    printf(1,"Average wait time per user-level thread is: %d\n",_wTime/(tableBuffer.max+4));
}

void myExit(){
    int rTime,wTime,tTime;
    retrieve_process_statistics(&tTime,&rTime,&wTime);
    //totalRTime += tTime;
    _rTime += rTime;
    _wTime += wTime;

    free(threads[kthread_id()-1].stack);
    kthread_exit();
}

void printDebug(char* name){
//       printf(1,"%s\t\t tid:%d id:%d stud:%d table:%d\n",name,kthread_id()-1, threads[kthread_id()-1].stud_id, restaurant.students_waiting, tableBuffer.counter);
}




//void getNextStud_id(){
//    currStud_id++;
//    if (currStud_id == idMAX) {
//        currStud_id = idMIN;
//    }
//}





/********************
 Student
 ********************/

/*
 -Eating process
 The following pseudo code should be applied by a thread after it successfully acquired the first two kinds of food (long eating process) and after acquiring the last one (short eating process).
 -Note the parameter iter_num is different for each type of eating.
 
 For i:1 to iter_num For j:1 to 1000
 t= 1
 For k:1 to 20
 t *= k;
 */
void eat(int iter_num){
    //printDebug("EatingStart");

    printf(1,"Student%d started %s eating process\n",threads[kthread_id()-1].stud_id,iter_num == SHORT_EATING_PROCESS_ITERATIONS ? "short" : "long");
    int t,k,j,i;
    for (i=1; i<iter_num; i++) {
        for (j=1; j<1000; j++) {
            t=1;
            for (k=1; k<20; k++) {
                t *= k;
            }
        }
    }
    //printDebug("EatingEnd");
    
    
}

//Each student must acquire all three kinds of food,
//The buffers must be synchronized, which means only one student can use a specific buffer at a given time.
void *accuireFood(){
    kthread_mutex_lock(tableBuffer.mutex_lock);
    if (restaurant.students_waiting > 0) {
        restaurant.students_waiting--;//one less student to worry about
        kthread_mutex_unlock(tableBuffer.mutex_lock);
        
        
        //First, each student acquires food type with (stud_id % 3) value assigned to it.
        kthread_mutex_lock(food[threads[kthread_id()-1].stud_id % 3].mutex_lock);
        while (food[threads[kthread_id()-1].stud_id % 3].counter == 0){
            printf(1,"Student%d waits for %d\n",threads[kthread_id()-1].stud_id,(threads[kthread_id()-1]).stud_id % 3);
            kthread_cond_wait(food[(threads[kthread_id()-1]).stud_id % 3].cv_empty, food[(threads[kthread_id()-1]).stud_id % 3].mutex_lock);
        }
        printDebug("accuireFood1");
        printf(1,"Student%d acquired %d\n",threads[kthread_id()-1].stud_id,(threads[kthread_id()-1]).stud_id % 3);
        food[threads[kthread_id()-1].stud_id % 3].counter--;
        kthread_mutex_unlock(food[threads[kthread_id()-1].stud_id % 3].mutex_lock);
        
        
        //Second, the student acquires food type with ( (stud_id+1) % 3 ) value assigned to it.
        kthread_mutex_lock(food[threads[kthread_id()-1].stud_id+1 % 3].mutex_lock);
        while (food[(threads[kthread_id()-1].stud_id+1) % 3].counter == 0){
            printf(1,"Student%d waits for %d\n",threads[kthread_id()-1].stud_id,(threads[kthread_id()-1]).stud_id % 3);
            kthread_cond_wait(food[threads[kthread_id()-1].stud_id+1 % 3].cv_empty,food[threads[kthread_id()-1].stud_id+1 % 3].mutex_lock);
        }
        printDebug("accuireFood2");
        printf(1,"Student%d acquired %d\n",threads[kthread_id()-1],(threads[kthread_id()-1]).stud_id % 3);
        food[threads[kthread_id()-1].stud_id+1 % 3].counter--;
        kthread_mutex_unlock(food[threads[kthread_id()-1].stud_id+1 % 3].mutex_lock);

        
        //At this point, the student must execute a long eating process on the 2 dishes acquired.
        eat(iter_long);
        
        
        //When the long eating process is done, the third and last dish ( (stud_id + 2) % 3) is to be acquired
        kthread_mutex_lock(food[threads[kthread_id()-1].stud_id+2 % 3].mutex_lock);
        while (food[threads[kthread_id()-1].stud_id+2 % 3].counter == 0){
            printf(1,"Student%d waits for %d\n",threads[kthread_id()-1].stud_id,(threads[kthread_id()-1]).stud_id % 3);
            kthread_cond_wait(food[threads[kthread_id()-1].stud_id+2 % 3].cv_empty,food[threads[kthread_id()-1].stud_id+2 % 3].mutex_lock);

        }
        printDebug("accuireFood3");
        printf(1,"Student%d acquired %d\n",threads[kthread_id()-1].stud_id,(threads[kthread_id()-1]).stud_id % 3);
        food[threads[kthread_id()-1].stud_id+2 % 3].counter--;
        kthread_mutex_unlock(food[threads[kthread_id()-1].stud_id+2 % 3].mutex_lock);

        
        //which leads to a short eating process.
        eat(iter_short);
        
        
        kthread_mutex_lock(tableBuffer.mutex_lock);
        tableBuffer.counter--;
        printf(1,"Student%d leaved the table\n",threads[kthread_id()-1].stud_id);
        //callHost();
        printDebug("**leaving");
        kthread_mutex_unlock(tableBuffer.mutex_lock);
    }
    //When the student has finished eating the last dish he leaves the table (the thread representing the student exits).
    //The simulation ends after all students have eaten all three dishes.
    
    kthread_mutex_lock(tableBuffer.mutex_lock);
    restaurant.students_done++;
    if (restaurant.students_done == restaurant.students_max) {
        exitFlag = 1;
    }
    kthread_mutex_unlock(tableBuffer.mutex_lock);

    
    printDebug("--exitRest");
    myExit();    
    return 0;
}

/********************
 Table        
 ********************/
//A WAITING student may join the table only when a seat become available, i.e. another student has finished dinning and left the table.
//Arriving students should be assigned a thread only after being seated by the host.
//The operation of adding new students to the table can be imagined as a host that welcomes new students and leads them to their seat.
//The host must verify first that there are any available seats.
//Otherwise, he should keep the students WAITING.
//When a seat has become available,
//the host notifies a WAITING student and puts him in his seat.
//void callHost(){
//    threads[kthread_id()-1].stud_id = -1;
//    
//    while (tableThreads[kthread_id()-1].stud_id == -1 && (restaurant.students > 0)) {
//        //uthread_yield();
//        kthread_cond_signal(tableBuffer.cv_full);
//        //mutex
//        //if(...)
//            
//        kthread_cond_wait(tableBuffer.cv_empty, tableBuffer.mutex_lock);
//        //free mutex
//    }
//    
//    if ((restaurant.students > 0)) {
//        printf(1,"Student%d joined the table\n",studThreadsIds_[kthread_id()-1]);
//    }
//}

void *host(){
    while (restaurant.students_waiting > 0) {
        kthread_mutex_lock(tableBuffer.mutex_lock);
        if(tableBuffer.counter < tableBuffer.max){
            tableBuffer.counter++;
            idMAX++;
            void* stack = malloc(MAX_STACK_SIZE);
            threads[idMAX].stack = stack;
            threads[idMAX].tid = kthread_create(accuireFood,stack,MAX_STACK_SIZE);
            threads[idMAX].stud_id = idMAX;
            kthread_cond_signal(tableBuffer.cv_empty);
            printDebug("+inviting");
        }
        
        if(tableBuffer.counter == tableBuffer.max){
            kthread_cond_wait(tableBuffer.cv_full, tableBuffer.mutex_lock);
        }
        kthread_mutex_unlock(tableBuffer.mutex_lock);
    }
    printDebug("--hostExit");
    return 0;
}

/********************
 Food        
 ********************/

//Only one item of food type can be added in a single operation of the waiter.
//Each food type has its own buffer that is maintained and refilled by a designated waiter.
//This (eager) waiter refills his buffer whenever it is not full.
//TODO(kthread):Also, a waiter can refill his buffer only in case it is not occupied by another student.

void *fillSalad(){
    while (exitFlag == 0) {

        kthread_mutex_lock(saladBuffer.mutex_lock);
        while (saladBuffer.counter < saladBuffer.max) {
                saladBuffer.counter++;
                printf(1,"WaiterSalad increased his buffer to %d/%d\n",saladBuffer.counter,saladBuffer.max);
                kthread_cond_signal(saladBuffer.cv_empty);
                //printDebug("+fillsalad");        
        }
        kthread_cond_wait(saladBuffer.cv_full,saladBuffer.mutex_lock);
        kthread_mutex_unlock(saladBuffer.mutex_lock);
    }
    printDebug("--SaladExit");
    myExit();    
    return 0;
}

void *fillSteak(){
    while (exitFlag == 0) {
        
        kthread_mutex_lock(steakBuffer.mutex_lock);
        while (steakBuffer.counter < steakBuffer.max) {
                steakBuffer.counter++;
                printf(1,"WaiterSteak increased his buffer to %d/%d\n",steakBuffer.counter,steakBuffer.max);
                kthread_cond_signal(steakBuffer.cv_empty);

            //printDebug("+fillsteak");
        }
        
        kthread_cond_wait(steakBuffer.cv_full,steakBuffer.mutex_lock);
        kthread_mutex_unlock(steakBuffer.mutex_lock);
    }
    printDebug("--SteakExit");
    myExit();    
    return 0;
    
}

void *fillPasta(){
    while (exitFlag == 0) {

        kthread_mutex_lock(pastaBuffer.mutex_lock);
        while (pastaBuffer.counter < pastaBuffer.max) {
                pastaBuffer.counter++;
                printf(1,"WaiterPasta increased his buffer to %d/%d\n",pastaBuffer.counter,pastaBuffer.max);
                kthread_cond_signal(pastaBuffer.cv_empty);

            //printDebug("+fillPasta");
        }

        kthread_cond_wait(pastaBuffer.cv_full,pastaBuffer.mutex_lock);
        kthread_mutex_unlock(pastaBuffer.mutex_lock);
    }
    printDebug("--PastaExit");
    myExit();
    return 0;
    
}


/*
 We model a restaurant. 
 it up to you decide when and where threads should yield.
 The threads must cooperate in order to complete the simulation.
 */
int main(int argc, char** argv){
    //Buffers sizes will be given in a configuration file as well, and will remain constant during the simulation.
    int i = 0, j = 0, m =0;
    int args[6] = { [0 ... 5] = 0 } ;
    int fp = open("ass2_conf.txt",0);
    char buf[1024];
    char value[5] = { '\0' };
    //printf(1,"here %d %d\n",fp,read( fp, line, sizeof(line)));
    if(read( fp, &buf, sizeof(buf) ) > 0){
        while (j<6) {
            while (buf[i] != '=' && i< sizeof(buf)) {
                //printf(1,"%c\n",buf[i]);
                i++;
            }
            i++;
            m=0;
            while (m<4) {
                value[m] = buf[i];
                m++;
                i++;
            }
            
            //printf(1,"valuse is: %s \n",value);
            args[j] = atoi(value);
            j++;
        }
    }
    close(fp);
    
    //The number of seats around the table, as well as the initial number of students will be given in a configuration file
    //and additional students might join the dining table during the simulation.
    //All buffers are full when the simulations start.
    saladBuffer.max = args[Salad_Buffer_Size];
    pastaBuffer.max = args[Pasta_Buffer_Size];
    steakBuffer.max = args[Steak_Buffer_Size];
    saladBuffer.counter = args[Salad_Buffer_Size];
    pastaBuffer.counter = args[Pasta_Buffer_Size];
    steakBuffer.counter = args[Steak_Buffer_Size];

    //A group of n hungry students arrive at the restaurant
    //Each Student has its unique id (stud_id), starting from 0 in consecutive manner.
    
    //There is a table with k seats around it.
    tableBuffer.max = args[Num_of_seats];    
    //If n>k the remaining students will have to wait until any seats will become available.
    if (args[Students_Initial] > args[Num_of_seats]) {
        //k of them take seats around the table.
        tableBuffer.counter = args[Num_of_seats];
    }else{
        tableBuffer.counter = args[Students_Initial];
    }
    printDebug("sec");
    
    printf(1,"%d %d %d\n",tableBuffer.counter, args[Num_of_seats], args[Students_Initial]);
    restaurant.students_waiting = args[Students_Joining];
    restaurant.students_done = 0;
    restaurant.students_max = args[Students_Joining] + args[Students_Initial];
        
    idMAX = tableBuffer.counter-1;//what is the next id that i can alloc

    pastaBuffer.cv_full = kthread_cond_alloc();
    pastaBuffer.cv_empty = kthread_cond_alloc();
    pastaBuffer.mutex_lock = kthread_mutex_alloc();
    steakBuffer.cv_full = kthread_cond_alloc();
    steakBuffer.cv_empty = kthread_cond_alloc();
    steakBuffer.mutex_lock = kthread_mutex_alloc();
    saladBuffer.cv_full = kthread_cond_alloc();
    saladBuffer.cv_empty = kthread_cond_alloc();
    saladBuffer.mutex_lock = kthread_mutex_alloc();
    tableBuffer.cv_full = kthread_cond_alloc();
    tableBuffer.cv_empty = kthread_cond_alloc();
    tableBuffer.mutex_lock = kthread_mutex_alloc();
    
    void* hostStack = malloc(MAX_STACK_SIZE);
    kthread_create(host,hostStack,MAX_STACK_SIZE);
    void* saladStack = malloc(MAX_STACK_SIZE);
    kthread_create(fillSalad,saladStack,MAX_STACK_SIZE);
    void* pastaStack = malloc(MAX_STACK_SIZE);
    kthread_create(fillPasta,pastaStack,MAX_STACK_SIZE);
    void* steakStack = malloc(MAX_STACK_SIZE);
    kthread_create(fillSteak,steakStack,MAX_STACK_SIZE);
    
    threads = malloc(sizeof(struct thread_t)*restaurant.students_max); // This array maps threads to student IDs
    
    //To model the problem, a thread should be created for each dinning student, each waiter and the host.
    //If k marks the number of seats around the table, then k+4 threads should be created.
    int ind;
    for (ind = 0;ind <restaurant.students_max && ind<tableBuffer.counter; ind++) {
        void* stack = malloc(MAX_STACK_SIZE);
        threads[ind].stud_id = ind;
        if (ind<tableBuffer.max) {
            threads[ind].stack = stack;
            threads[ind].tid = kthread_create(accuireFood,stack,MAX_STACK_SIZE);
        }
    }
    
    for (ind = 0; ind < tableBuffer.max+4; ind++) {
        kthread_join(ind);
    }
    
    kthread_cond_dealloc(pastaBuffer.cv_full);
    kthread_cond_dealloc(pastaBuffer.cv_empty);
    kthread_cond_dealloc(pastaBuffer.mutex_lock);
    kthread_cond_dealloc(steakBuffer.cv_full);
    kthread_cond_dealloc(steakBuffer.cv_empty);
    kthread_cond_dealloc(steakBuffer.mutex_lock);
    kthread_cond_dealloc(saladBuffer.cv_full);
    kthread_cond_dealloc(saladBuffer.cv_empty);
    kthread_cond_dealloc(saladBuffer.mutex_lock);
    kthread_cond_dealloc(tableBuffer.cv_full);
    kthread_cond_dealloc(tableBuffer.cv_empty);
    kthread_cond_dealloc(tableBuffer.mutex_lock);
    
    printStats();
    
    free(threads);
    free(pastaStack);
    free(steakStack);
    free(saladStack);
   // free(hostStack);
    
    kthread_exit();

    return 1;
}



